package org.colswe.solestac.control;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Persistence;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.NonUniqueResultException;
import javax.persistence.NoResultException;
import org.colswe.solestac.dao.RoleDao;
import org.colswe.solestac.dao.UserDao;
import org.colswe.solestac.entity.FinalUser;
import org.colswe.solestac.entity.Rol;
import org.colswe.solestac.VO.RoleVO;
import org.colswe.solestac.VO.UserVO;

/**
 *
 * @author Laarguelless
 */
public class AdministrationControl {
    
    /**
     * ALL GOOD = 0.
     */
    public static final int ALL_GOOD = 0;
    /**
     * ERROR = -8.
     */
    public static final int ERROR = -8;
    /**
     * INVALID DOCUMENT = -7.
     */
    public static final int INVALID_DOCUMENT = -7;
    /**
     * INVALID NAME FORMAT = -1.
     */
    public static final int INVALID_NAME_FORMAT = -1;
    /**
     * INVALID NAME LENGTH = -2.
     */
    public static final int INVALID_NAME_LENGTH = -2;
    /**
     * INVALID PASSWORD FORMAT = -3.
     */
    public static final int INVALID_PASSWORD_FORMAT = -3;
    /**
     * INVALID PASSWORD LENGTH = -4.
     */
    public static final int INVALID_PASSWORD_LENGTH = -4;
    /**
     * NO ROL SELECTED = -9.
     */
    public static final int NO_ROL_SELECTED = -9;
    /**
     * PASSWORD DONT MATCH = -6.
     */
    public static final int PASSWORD_DONT_MATCH = -6;
    /**
     * USERNAME ALREADY EXIST = -5.
     */
    public static final int USERNAME_ALREADY_EXIST = -5;

    /**EntityManagerFactory created to manage the Persistenc Unit.
     */
    private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("SolestacWebPU");
    /**LogControl object to record events in the log.
     */
    private LogControl log;

    /**Default Constructor.
     */
    public AdministrationControl() {
        createlog();
    }
    
    /**Creation of the Log object to record events.
     */
    private void createlog() {
        try {
            this.log = new LogControl("org.colswe.solestac.control.AdministrationControl");
        } catch (IOException ex) {
            ex.printStackTrace(System.err);
        }
    }

    /**Search of users with a given username.
     * @param un The username given
     * @return A list with the users that use the given username
     */
    public List<UserVO> searchByName(final String un) {

        List<FinalUser> users;
        List<UserVO> usersVO = null;
        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        try {
            UserDao userDao = new UserDao();
            users = userDao.searchByUsername(un, em);
            usersVO = convertUsers(users);
        } catch (NoResultException ex) {
            usersVO = new ArrayList<UserVO>();
        } catch (NonUniqueResultException ex) {
            throw ex;
        } finally {
            em.close();
            return usersVO;
        }
    }

    /**Creation of the user with the data contained in the ValueObject.
     * @param userVO The object representing the new user
     * @return true or false
     */
    public int createUser(final UserVO userVO) {
        int result = verifyInfo(userVO);
        if (result != 0) {
            return result;
        }
        EntityManager em = emf.createEntityManager();
        FinalUser user = convertVO(userVO);
        UserDao userDao = new UserDao();
        em.getTransaction().begin();
        try {
            RoleDao roleDao = new RoleDao();
            Rol role = roleDao.search(userVO.getRole(), em);
            role.getFinalUserList().add(user);
            roleDao.update(role, em);
            userDao.create(user, em);
            em.getTransaction().commit();
            return ALL_GOOD;
        } catch (NoResultException ex) {
            log.logging(ex);
            return ERROR;
        } catch (NonUniqueResultException ex) {
            log.logging(ex);
            return ERROR;
        } finally {
            em.close();
        }

    }

    /**Search users by role.
     * @param rn A role name used to find a set of users
     * @return A List<FinalUser> that contains all users from a particular role
     */
    private List<UserVO> searchByRole(final String rn) {
        List<UserVO> usersVO = null;
        List<FinalUser> users;
        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        try {
            RoleDao roleDao = new RoleDao();
            Rol role = roleDao.search(rn, em);
            users = role.getFinalUserList();
            usersVO = convertUsers(users);

        } catch (NoResultException ex) {
            usersVO = new ArrayList<UserVO>();
        } catch (NonUniqueResultException ex) {
            throw ex;
        } finally {
            if (em.isOpen()) {
                em.close();
            }
            return usersVO;
        }
    }

    /**Search an user by ID.
     * @param uId The Identification number of a user
     * @return a UserVO instance that contains the user data
     */
    private List<UserVO> searchById(final String uId) {
        List<UserVO> usersVO = null;
        List<FinalUser> users;
        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        try {
            UserDao userDao = new UserDao();
            users = userDao.searchByIdentification(uId, em);
            usersVO = convertUsers(users);
        } catch (NoResultException ex) {
            usersVO = new ArrayList<UserVO>();
        } catch (NonUniqueResultException ex) {
            throw ex;
        } finally {
            if (em.isOpen()) {
                em.close();
            }
            return usersVO;
        }
    }

    /**Creates a UserVO from a FinalUser data.
     * @param user A FinalUser instance
     * @return a UserVO
     */
    private UserVO convert(final FinalUser user) {
        UserVO userVO = new UserVO();
        userVO.setId(user.getId());
        userVO.setDocument(user.getDocument());
        userVO.setUsername(user.getUserName().toLowerCase());
        userVO.setRole(user.getRolId().getName());
        userVO.setPassword(user.getPassword());
        return userVO;
    }

    /**Converts a rol object into a role_VO object.
     * @param rol The rol object to convert
     * @return The role_VO object
     */
    private RoleVO convert(final Rol rol) {
        RoleVO roleVO = new RoleVO();
        roleVO.setId(rol.getId());
        roleVO.setName(rol.getName());
        return roleVO;
    }

    /**Converts a rol objects list into a role_VO objects list.
     * @param roles The list of rol objects to convert
     * @return The list of rol_VO objects
     */
    private List<RoleVO> convertRoles(final List<Rol> roles) {
        List<RoleVO> rolesVO = new ArrayList<RoleVO>();
        for (Rol rol : roles) {
            RoleVO rolVO = convert(rol);
            rolesVO.add(rolVO);
        }
        return rolesVO;
    }

    /**Convert an userVO object into a FinalUser object.
     * @param userVO The object to convert
     * @return The FinalUser object
     */
    private FinalUser convertVO(final UserVO userVO) {

        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        FinalUser user = null;
        try {
            user = new FinalUser();
            user.setId(userVO.getId());
            user.setUserName(userVO.getUsername().toLowerCase());
            user.setName(userVO.getUsername());
            user.setPassword(userVO.getPassword());
            user.setRolId(new RoleDao().search(userVO.getRole(), em));
            user.setDocument(userVO.getDocument());
        } catch (NoResultException ex) {
            throw ex;
        } catch (NonUniqueResultException ex) {
            throw ex;
        } finally {
            em.close();
            return user;
        }
    }

    /**Create a List<User_VO> from a List<FinalUser> using the private method convert in a for-each loop.
     * @param users A List of FinalUser
     * @return a List of UserVO
     */
    private List<UserVO> convertUsers(final List<FinalUser> users) {
        List<UserVO> usersVO = new ArrayList<UserVO>();
        for (FinalUser x : users) {
            UserVO userVO = convert(x);
            usersVO.add(userVO);
        }
        return usersVO;
    }

    /**Verifies the format of the username string.
     * @param username The String given
     * @return true if the username format is correct. false otherwise
     */
    public boolean verifyUsernameFormat(final String username) {
        return (username.matches("^[A-Za-z0-9]*$"));
    }

    /**Verifies the length of the username string.
     * @param username The string given
     * @return true if the username length is correct. false otherwise
     */
    public boolean verifyUsernameLength(final String username) {
        return (username.matches(".{5,20}"));
    }

    /**Verifies the format of the password string.
     * @param password The String given
     * @return true if the password format is correct. false otherwise
     */
    public boolean verifyPasswordFormat(final String password) {
        return (password.matches("((?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).*)")
                && password.matches("^[A-Za-z0-9]*$"));
    }

    /**Verifies the length of the password string.
     * @param password The String given
     * @return true if the password length is correct. false otherwise
     */
    public boolean verifyPasswordLength(final String password) {
        return (password.matches(".{5,30}"));
    }

    
    /**Verifies if the username given by the new user already exists.
     * @param username The String given
     * @return true if the username exists in the data base. False otherwise
     */
    public boolean existsUsername(final String username) {
        UserDao userDao = new UserDao();
        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        try {
            List<FinalUser> users = userDao.searchByUsername(username, em);
            return (!users.isEmpty());
        } catch (NoResultException ex) {
            //log.logging(ex); If the new user doesn't exists, obviously, userDao will throw this exception.
            return false;
        } catch (NonUniqueResultException ex) {
            log.logging(ex);
            return false;
        } finally {
            em.close();            
        }
    }
    
    /**Verifies if the new username given by the user already exists.
     * @param username the String given
     * @param id the ID from the user, to confirm the changes
     * @return true if the username exists in the data base. False otherwise
     */
    public boolean existsUsernameUpdate(final String username, final long id) {
        UserDao userDao = new UserDao();
        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        try {
            List<FinalUser> users = userDao.searchByUsername(username, em);
            if (users.isEmpty()) {
                return false;
            } else {
                return !(users.get(0).getId() == id);
            }
        } catch (NoResultException ex) {
            //log.logging(ex); If the new user doesn't exists, obviously, userDao will throw this exception.
            return false;
        } catch (NonUniqueResultException ex) {
            log.logging(ex);
            return false;
        } finally {
            em.close();            
        }
    }

    /**Verifies the data contained in the UserVO object.
     * @param user The object to verify the information
     * @return The number indicating the result of the verification
     */
    public int verifyInfo(final UserVO user) {
        try {
            if (!verifyUsernameFormat(user.getUsername())) {
                return INVALID_NAME_FORMAT;
            }
            if (!verifyUsernameLength(user.getUsername())) {
                return INVALID_NAME_LENGTH;
            }           
            if (!verifyPasswordFormat(user.getPassword())) {
                return INVALID_PASSWORD_FORMAT;
            }
            if (!verifyPasswordLength(user.getPassword())) {
                return INVALID_PASSWORD_LENGTH;
            }
             if (existsUsername(user.getUsername())) {
                return USERNAME_ALREADY_EXIST;
            }
            if (!user.getPassword().equals(user.getRepassword())) {
                return PASSWORD_DONT_MATCH;
            }
            if (!verifyDocument(user.getDocument())) {
                return INVALID_DOCUMENT;
            }
            if (user.getRole().equals("--Seleccione Rol--")) {
                return NO_ROL_SELECTED;
            }
            return ALL_GOOD;
        } catch (Exception ex) {
            log.logging(ex);
            return ERROR;
        }
    }

    /**Verifies the info given by the user in the actualization process.
     * @param user The UserVO object with the info.
     * @return The number indicating the result of the verification
     */
    public int verifyUpdateInfo(final UserVO user) {
    try {
        if (!verifyUsernameFormat(user.getUsername())) {
            return INVALID_NAME_FORMAT;
        }
        if (!verifyUsernameLength(user.getUsername())) {
            return INVALID_NAME_LENGTH;
        } 
        if (!verifyPasswordFormat(user.getPassword())) {
            return INVALID_PASSWORD_FORMAT;
        }
        if (!verifyPasswordLength(user.getPassword())) {
            return INVALID_PASSWORD_LENGTH;
        }
        if (existsUsernameUpdate(user.getUsername(), user.getId())) {
            return USERNAME_ALREADY_EXIST;
        }
        if (!user.getPassword().equals(user.getRepassword())) {
            return PASSWORD_DONT_MATCH;
        }
        if (!verifyDocument(user.getDocument())) {
            return INVALID_DOCUMENT;
        }
        if (user.getRole().equals("--Seleccione Rol--")) {
            return NO_ROL_SELECTED;
        }
        return ALL_GOOD;
    } catch (Exception ex) {
        log.logging(ex);
        return ERROR;
    }
    }
    
    /**Updates the user.
     * @param userVO The object with the information of the new user
     * @return The number indicating the result of the actualization
     */
    public int updateUser(final UserVO userVO) {
        int result = verifyUpdateInfo(userVO);
        if (result != 0) {
            return result;
        }
        EntityManager em = emf.createEntityManager();
        try {
            FinalUser user = convertVO(userVO);
            UserDao userDao = new UserDao();
            em.getTransaction().begin();
            userDao.update(user, em);
            em.getTransaction().commit();
            return ALL_GOOD;
        } catch (NoResultException ex) {
            log.logging(ex);
            return ERROR;
        } catch (NonUniqueResultException ex) {
            log.logging(ex);
            return ERROR;
        } finally {
            em.close();
        }

    }

    /**Deletes the user.
     * @param userVO The object with the information of the user to be deleted.
     * @return The number indicating the result of the operation
     */
    public boolean deleteUser(final UserVO userVO) {
        EntityManager em = emf.createEntityManager();
        UserDao userDao = new UserDao();
        RoleDao roleDao = new RoleDao();
        em.getTransaction().begin();
        try {
            FinalUser user = convertVO(convertUsers(userDao.searchByUsername(userVO.getUsername().toLowerCase(), em)).get(0));
            Rol rol = roleDao.search(userVO.getRole(), em);
            rol.getFinalUserList().remove(user);
            roleDao.update(rol, em);
            userDao.delete(user, em);
            em.getTransaction().commit();
            return true;
        } catch (NoResultException ex) {
            log.logging(ex);
            return false;
        } catch (NonUniqueResultException ex) {
            log.logging(ex);
            return false;
        } finally {
            em.close();
        }
    }

    /**Verifies the format of the document given.
     * @param document The string given.
     * @return true if the document format is correct. false otherwise
     */
    private boolean verifyDocument(final String document) {
        return (document.matches("^[0-9]{6,12}"));
    }

    /**Searches the users based in the parameters given.
     * @param username The username to search
     * @param document The document to search
     * @param rln The rol name to search
     * @return The users that matches the search parameters
     */
    public List<UserVO> search(final String username, final String document, final String rln) {
        String usernameLC = username.toLowerCase();
        List<UserVO> usersVO = new ArrayList<UserVO>();
        List<FinalUser> users;
        UserDao userDao = new UserDao();
        RoleDao roleDao = new RoleDao();
        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();        
        try {
           
            if (usernameLC.length() > 0 && document.length() > 0 && rln.length() > 0) {
                long rId = roleDao.search(rln, em).getId();
                users = userDao.searchByUserAndDocAndRol(usernameLC, document, rId, em);
                usersVO = convertUsers(users);
                return usersVO;
            }
            if (usernameLC.length() > 0 && document.length() > 0) {
                users = userDao.searchByUserAndDoc(usernameLC, document, em);
                usersVO = convertUsers(users);
                return usersVO;
            }
            if (usernameLC.length() > 0 && rln.length() > 0) {
                long rId = roleDao.search(rln, em).getId();
                users = userDao.searchByUserAndRol(usernameLC, rId, em);
                usersVO = convertUsers(users);
                return usersVO;
            }
            if (rln.length() > 0 && document.length() > 0) {
                long rId = roleDao.search(rln, em).getId();
                users = userDao.searchByDocAndRol(document, rId, em);
                usersVO = convertUsers(users);
                return usersVO;
            }
            if (usernameLC.length() > 0) {
                em.close();
                usersVO = searchByName(usernameLC);
                return usersVO;
            }
            if (rln.length() > 0) {
                em.close();
                usersVO = searchByRole(rln); 
                return usersVO;
            }
            if (document.length() > 0) {
                em.close();
                usersVO = searchById(document);
                return usersVO;
            }
            users = userDao.searchAll(em);
            usersVO = convertUsers(users);
            
        } catch (NoResultException ex) {
            usersVO = new ArrayList<UserVO>();
            return null;
        } catch (NumberFormatException ex) {
            log.logging(ex);
            return null;
        } finally {
            if (em.isOpen()) {
                em.close();
            }
            return usersVO;
        }
    }

    /**Gets all roles in the system.
     * @return The list of roles in Value Objects
     */
    public List<RoleVO> getAllRoles() {
        List<RoleVO> rolesVO = null;
        RoleDao roleDao = new RoleDao();
        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        try {
            List<Rol> roles = roleDao.searchAll(em);
            rolesVO = convertRoles(roles);
        } catch (NoResultException ex) {
            log.logging(ex);
        } finally {
            em.close();
            return rolesVO;
        }
    }
}
